其他
Python进阶——如何正确使用魔法方法?(下)
阅读本文大约需要 10 分钟。
比较操作
__cmp__
__eq__
__ne__
__lt__
__gt__
__cmp__
__cmp__
来实现比较操作。def __init__(self, uid):
self.uid = uid
def __cmp__(self, other):
if self.uid == other.uid:
return 0
if self.uid > other.uid:
return 1
return -1
p1 = Person(1)
p2 = Person(2)
print p1 > p2 # False
print p1 < p2 # True
print p1 == p2 # False
如果 __cmp__
返回大于 0 的整数(一般为1),说明 self > other如果 __cmp__
返回大于 0 的整数(一般为-1),说明 self < other如果 __cmp__
返回 0,说明 self == other
__cmp__
就无法很好地实现这个逻辑了,所以它只适用于通用的比较逻辑。__eq__
、__ne__
、__lt__
、__gt__
这些魔法方法了,我们看下面这个例子。class Person(object):
def __init__(self, uid, name, salary):
self.uid = uid
self.name = name
self.salary = salary
def __eq__(self, other):
"""对象 == 判断"""
return self.uid == other.uid
def __ne__(self, other):
"""对象 != 判断"""
return self.uid != other.uid
def __lt__(self, other):
"""对象 < 判断 根据len(name)"""
return len(self.name) < len(other.name)
def __gt__(self, other):
"""对象 > 判断 根据alary"""
return self.salary > other.salary
p1 = Person(1, 'zhangsan', 1000)
p2 = Person(1, 'lisi', 2000)
p3 = Person(1, 'wangwu', 3000)
print p1 == p1 # uid 是否相同
print p1 != p2 # uid 是否不同
print p2 < p3 # name 长度比较
print p3 > p2 # salary 比较
__eq__
__eq__
我们在上一篇文章已经介绍过,它配合 __hash__
方法,可以判断两个对象是否相等。uid
这个属性。__ne__
__ne__
方法,在这个例子中,我们也是根据 uid
来判断的。__lt__
__lt__
方法,在这个例子中,我们根据 name
的长度来做的比较。__gt__
__gt__
方法,在这个例子中,我们根据 salary
属性判断。在 Python3 中, __cmp__
被取消了,因为它和其他魔法方法存在功能上的重复。
容器类操作
__setitem__
__getitem__
__delitem__
__len__
__iter__
__contains__
__reversed__
字典 元组 列表 字符串
class MyList(object):
"""自己实现一个list"""
def __init__(self, values=None):
# 初始化自定义list
self.values = values or []
def __setitem__(self, key, value):
# 添加元素
self.values[key] = value
def __getitem__(self, key):
# 获取元素
return self.values[key]
def __delitem__(self, key):
# 删除元素
del self.values[key]
def __len__(self):
# 自定义list的元素个数
return len(self.values)
def __iter__(self):
# 可迭代
return self
def next(self):
# 迭代的具体细节
# 如果__iter__返回self 则必须实现此方法
if self._index >= len(self.values):
raise StopIteration()
value = self.values[self._index]
self._index += 1
return value
def __contains__(self, key):
# 元素是否在自定义list中
return key in self.values
def __reversed__(self):
# 反转
return list(reversed(self.values))
# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])
print my_list[0] # __getitem__
my_list[1] = 20 # __setitem__
print 1 in my_list # __contains__
print len(my_list) # __len__
print [i for i in my_list] # __iter__
del my_list[0] # __del__
reversed_list = reversed(my_list) # __reversed__
print [i for i in reversed_list] # __iter__
list
一样,通过切片的方式添加、获取、删除、迭代元素了。__setitem__
my_list[1] = 20
时,就会调用 __setitem__
方法,这个方法主要用于向容器内添加元素。__getitem__
my_list[0]
时,就会调用 __getitem__
方法,这个方法主要用于从容器中读取元素。__delitem__
del my_list[0]
时,就会调用 __delitem__
方法,这个方法主要用于从容器中删除元素。__len__
len(my_list)
时,就会调用 __len__
方法,这个方法主要用于读取容器内元素的数量。__iter__
[i for i in my_list]
?就是因为我们定义了 __iter__
。返回 iter(obj)
:代表使用obj
对象的迭代协议,一般obj
是内置的容器对象返回 self
:代表迭代的逻辑由本类来实现,此时需要重写next
方法,实现自定义的迭代逻辑
__iter__
返回的是 self
,所以我们需要定义 next
方法,实现自己的迭代细节。next
方法使用一个索引变量,用于记录当前迭代的位置,这个方法每次被调用时,都会返回一个元素,当所有元素都迭代完成后,这个方法会返回 StopIteration
异常,此时 for
会停止迭代。在 Python3 中,已不再使用 next 方法,取而代之的是 __next__
。
__contains__
1 in my_list
时触发,用于判断某个元素是否存在于容器中。__reversed__
reversed(my_list)
时触发,用于反转容器的元素,具体的反转逻辑我们也可以自己实现。可调用对象
__call__
。class Circle(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, x, y):
self.x = x
self.y = y
c = Circle(10, 20) # __init__
print c.x, c.y # 10 20
c(100, 200) # 调用instance() 触发__call__
print c.x, c.y # 100 200
Circle
实例 c
,此时会调用 __init__
方法,这个很好理解。c
又做了调用 c(100, 200)
,注意,此时的 c
是一个实例对象,当我们这样执行时,其实它调用的就是 __call__
。这样一来,我们就可以把实例当做一个方法来执行。__call__
方法,就可以传入自定义参数实现自己的逻辑。序列化
pickle
,当我们使用这个模块序列化一个实例时,也可以通过魔法方法来实现自己的逻辑,这些魔法方法包括:__getstate__
__setstate__
class Person(object):
def __init__(self, name, age, birthday):
self.name = name
self.age = age
self.birthday = birthday
def __getstate__(self):
# 执行 pick.dumps 时 忽略 age 属性
return {
'name': self.name,
'birthday': self.birthday
}
def __setstate__(self, state):
# 执行 pick.loads 时 忽略 age 属性
self.name = state['name']
self.birthday = state['birthday']
person = Person('zhangsan', 20, date(2017, 2, 23))
pickled_person = pickle.dumps(person) # __getstate__
p = pickle.loads(pickled_person) # __setstate__
print p.name, p.birthday
print p.age # AttributeError: 'Person' object has no attribute 'age'
__getstate__
Person
对象,其中包括 3 个属性:name
、age
、birthday
。pickle.dumps(person)
时,__getstate__
方法就会被调用,在这里我们忽略了 Person
对象的 age
属性,那么 person
在序列化时,就只会对其他两个属性进行保存。__setstate__
pickle.loads(pickled_person)
时,__setstate__
会被调用,其中入参就是 __getstate__
返回的结果。__setstate__
方法,我们从入参中取得了被序列化的 dict
,然后从 dict
中取出对应的属性,就达到了反序列的效果。其他魔法方法
总结
list
、dict
那样,方便地去获取容器里的元素、迭代数据等等。可调用对象魔法方法,可以把一个实例当做方法来调用。序列化的魔法方法,可以修改一个实例的序列化和反序列化逻辑。